/* vccd/session.c 
 * 
 * This file is part of vccd. 
 * 
 * vccd is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * vccd is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with vccd. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/times.h>

#include <config.h>
#include <vcc/vccd.h>



struct klist_node sessions = KLIST_NODE_INIT(&sessions);
int next_sid = 1;


/* it takes 3 * sizeof(struct klist_node) size to create a new session, it's 
 * not a small one. */

int create_session(int fd, char *name) {
	struct klist_node *n, *s, *p;

	if (unlikely(!(n = amalloc(sizeof(struct klist_node))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	if (unlikely(!(s = amalloc(sizeof(struct klist_node))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	if (unlikely(!(p = amalloc(sizeof(struct klist_node))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	klist_init(n);
	klist_init(s);
	klist_init(p);

	n->value = next_sid++;

	if (unlikely(!(n->usrname = strndup(name, USRNAME_SIZE)))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}
	
	klist_add(&sessions, n);

	s->value = fd;
	s->nusers = 1;
	n->session = s;

	p->value = fd;
	p->connection = find_connection(fd);
	
	klist_add(s, p);

	vcc_log("New session '%s' created by %d. \n", name, fd);
	vcc_info("INEWSESS %s %d\n", name, fd);

	return 0;
}


struct klist_node *find_session(int sid) {
	struct klist_node *n;

	for (n = sessions.next; n != &sessions; n = n->next) 
		if (n->value == sid) 
			return n;

	vcc_log("--- session not found. \n");

	return NULL;
}


int fd_in_session(struct klist_node *session, int fd) {
	struct klist_node *s = session->session, *n;

	for (n = s->next; n != s; n = n->next) 
		if (n->value == fd) 
			return 1;

	return 0;
}


int join_session(int sid, int fd) {
	struct klist_node *session, *n;
	static const char *join_msg = "%s joined session %s, now there are %d users in this session. \n";
	char *t;

	if (unlikely(!(session = find_session(sid)))) 
		return 1;

	if (fd_in_session(session, fd)) {
		vcc_log("%d is already in the session %d. \n", fd, sid);

		return 0;
	}

	if (unlikely(!(n = amalloc(sizeof(struct klist_node))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	if (unlikely(!(t = amalloc(128)))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	klist_init(n);

	n->value = fd;
	n->connection = find_connection(fd);

	klist_add(session->session, n);

	sprintf(t, join_msg, n->connection->usrname, session->usrname, ++session->session->nusers);
	session_broadcast(sid, t, "system", 0, 0);

	vcc_log("%d joined session %d. \n", fd, sid);

	afree(t, 128);

	return 0;
}


int session_broadcast(int sid, char *msg, char *usrname, int except, int flags) {
	struct klist_node 	*session, *n;
	struct vcc_request 	req, *out;
	int 			i;

	if (unlikely(!(session = find_session(sid)))) 
		return 1;

	memset(&req, 0, REQ_SIZE);

	req.magic = htonl(VCC_MAGIC);
	req.reqtype = htonl(REQ_MSG_NEW);
	req.uid = 0;
	req.session = htonl(sid);
	req.flags = flags;

	memcpy(req.msg, msg, MSG_SIZE);
	strncpy(req.usrname, usrname, USRNAME_SIZE);

	i = 0;
	
	for (n = session->session->next; n != session->session; n = n->next) {
		if (n->value == except) 
			continue;

		if (unlikely(!(out = amalloc(REQ_SIZE)))) {
			vcc_log("*** malloc() failed\n");

			return 1;
		}

		memcpy(out, &req, REQ_SIZE);
		vcc_send_req(n->connection, out);

		i++;
	}

	vcc_write_all();
	vcc_log("(%d) Message sent to %d users. \n", sid, i);

	return 0;
}


int session_name(int fd, struct vcc_request *req) {
	struct vcc_request *out;
	struct klist_node *n;
	char *p;

	if (unlikely(!(out = amalloc(sizeof(struct vcc_request))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	out->magic = htonl(VCC_MAGIC);
	out->reqtype = htonl(REQ_CTL_SENAME);

	if (req->session == 0) 
		p = "broadcast";

	else {
		if (unlikely(!(n = find_session(ntohl(req->session))))) {
			out->session = htonl(-1);
			vcc_send_req(find_connection(fd), out);

			vcc_log("%d sends a bad session number. \n", fd);
			return 1;
		}

		p = n->usrname;
	}

	strcpy(out->msg, p);

	out->session = req->session;
	vcc_send_req(find_connection(fd), out);

	vcc_log("sename: %s sent to %d\n", p, fd);

	return 0;
}


int send_sessions(int fd) {
	struct klist_node 	*c, *n;
	struct vcc_request 	*req;
	int 			i;

	if (unlikely(!(c = find_connection(fd)))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	if (unlikely(!(req = amalloc(REQ_SIZE)))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	req->magic = htonl(VCC_MAGIC);
	req->reqtype = htonl(REQ_CTL_SESS);

	for (n = sessions.next, i = 0; n != &sessions; n = n->next, i++) {
		strncpy(req->msg + i * USRNAME_SIZE, n->usrname, USRNAME_SIZE);

		if (unlikely(i >= (int) MSG_SIZE / USRNAME_SIZE)) {
			vcc_log("--- too many sessions. \n");

			break;
		}
	}

	req->uid = htonl(i);
	
	vcc_send_req(find_connection(fd), req);
	vcc_log("%d session names sent to %d. \n", i, fd);

	return 0;
}



